<?php
require_once 'Zend/Json/Exception.php';
class Zend_Json_Encoder
{
    protected $_visited = array();
    protected function __construct()
    {
    }
    public static function encode($value)
    {
        $encoder = new Zend_Json_Encoder();

        return $encoder->_encodeValue($value);
    }
    protected function _encodeValue(&$value)
    {
        if (is_object($value)) {
            return $this->_encodeObject($value);
        } else if (is_array($value)) {
            return $this->_encodeArray($value);
        }

        return $this->_encodeDatum($value);
    }
    protected function _encodeObject(&$value)
    {
        if ($this->_wasVisited($value)) {
            throw new Zend_Json_Exception(
                'Cycles not supported in JSON encoding, cycle introduced by '
                . 'class "' . get_class($value) . '"'
            );
        }

        $this->_visited[] = $value;

        $props = '';
        foreach (get_object_vars($value) as $name => $propValue) {
            if (isset($propValue)) {
                $props .= ', '
                        . $this->_encodeValue($name)
                        . ' : '
                        . $this->_encodeValue($propValue);
            }
        }
        return '{' . substr($props,1) . '}';

    }
    protected function _wasVisited(&$value)
    {
        if (in_array($value, $this->_visited, true)) {
            return true;
        }

        return false;
    }
    protected function _encodeArray(&$array)
    {
        $tmpArray = array();

        if (array_keys($array) !== range(0, count($array) - 1)) {
            $result = '{';
            foreach ($array as $key => $value) {
                $key = (string) $key;
                $tmpArray[] = $this->_encodeString($key)
                            . ' : '
                            . $this->_encodeValue($value);
            }
            $result .= implode(', ', $tmpArray);
            $result .= '}';
        } else {
            $result = '[';
            $length = count($array);
            for ($i = 0; $i < $length; $i++) {
                $tmpArray[] = $this->_encodeValue($array[$i]);
            }
            $result .= implode(', ', $tmpArray);
            $result .= ']';
        }

        return $result;
    }
    protected function _encodeDatum(&$value)
    {
        $result = 'null';

        if (is_numeric($value)) {
            $result = (string)$value;
        } elseif (is_string($value)) {
            $result = $this->_encodeString($value);
        } elseif (is_bool($value)) {
            $result = $value ? 'true' : 'false';
        }

        return $result;
    }
    protected function _encodeString(&$string)
    {
        $search  = array('\\', "\n", "\t", "\r", "\b", "\f", '"');
        $replace = array('\\\\', '\\n', '\\t', '\\r', '\\b', '\\f', '\"');
        $string  = str_replace($search, $replace, $string);
        $string = str_replace(array(chr(0x08), chr(0x0C)), array('\b', '\f'), $string);

        return '"' . $string . '"';
    }
    static private function _encodeConstants(ReflectionClass $cls)
    {
        $result    = "constants : {";
        $constants = $cls->getConstants();

        $tmpArray = array();
        if (!empty($constants)) {
            foreach ($constants as $key => $value) {
                $tmpArray[] = "$key: " . self::encode($value);
            }

            $result .= implode(', ', $tmpArray);
        }

        return $result . "}";
    }
    static private function _encodeMethods(ReflectionClass $cls)
    {
        $methods = $cls->getMethods();
        $result = 'methods : {';

        $started = false;
        foreach ($methods as $method) {
            if (! $method->isPublic() || !$method->isUserDefined()) {
                continue;
            }

            if ($started) {
                $result .= ", ";
            }
            $started = true;

            $result .= " " .$method->getName(). ': function(';

            if ('__construct' != $method->getName()) {
                $parameters  = $method->getParameters();
                $paramCount  = count($parameters);
                $argsStarted = false;

                $argNames = "var argNames = [";
                foreach ($parameters as $param) {
                    if ($argsStarted) {
                        $result .= ', ';
                    }

                    $result .= $param->getName();

                    if ($argsStarted) {
                        $argNames .= ', ';
                    }

                    $argNames .= '"' . $param->getName() . '"';

                    $argsStarted = true;
                }
                $argNames .= "];";

                $result .= ") {"
                         . $argNames
                         . "var result = ZAjaxEngine.invokeRemoteMethod("
                         . "this, '" . $method->getName()
                         . "', argNames, arguments);"
                         . "return(result);}";
            } else {
                $result .= ") {}";
            }
        }

        return $result . "}";
    }

    static private function _encodeVariables(ReflectionClass $cls)
    {
        $properties = $cls->getProperties();
        $propValues = get_class_vars($cls->getName());
        $result = "variables : {";
        $cnt = 0;

        $tmpArray = array();
        foreach ($properties as $prop) {
            if (! $prop->isPublic()) {
                continue;
            }

            $tmpArray[] = $prop->getName()
                        . self::encode($propValues[$prop->getName()]);
        }
        $result .= implode(',', $tmpArray);

        return $result . "}";
    }
    static public function encodeClass($className, $package = '')
    {
        $cls = new ReflectionClass($className);
        if (! $cls->isInstantiable()) {
            throw new Zend_Json_Exception("$className must be instantiable");
        }

        return "Class.create('$package$className', {"
                . self::_encodeConstants($cls)    .", "
                . self::_encodeMethods($cls)      .", "
                . self::_encodeVariables($cls)    .'});';
    }
    static public function encodeClasses($classNames, $package = '')
    {
        $result = '';
        foreach ($classNames as $className) {
            $result .= self::encodeClass($className, $package);
        }

        return $result;
    }

}

